package cn.flying.cloud.gateway.configuration;

import javax.annotation.Resource;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;


/**
 * 网关通用配置
 *
 * @author: admin
 * @date: 2023年06月27日 11:16
 * @version: 1.0
 */
@Configuration
public class GatewayConfiguration {

    @Resource
    private CustomOauthClientProperties oauthClientProperties;

    /**
     * webflux默认使用netty作为服务器，如需要调整header、post大小配置
     *
     * @return
     */
    @Bean
    public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> nettyCustomizer() {
        return factory -> factory.addServerCustomizers(httpServer ->
                httpServer.httpRequestDecoder(decoder -> decoder.maxHeaderSize(1024 * 1024 * 10 * 10)) // 设置最大请求头大小为 1MB 默认是8192（8kb，8*1024）
        );
    }

    /**
     * 网关全局跨域配置
     *
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        // 允许携带 Authorization 头 允许cookies跨域
        config.setAllowCredentials(true);
        // 允许所有域，当请求头，允许向该服务器提交请求的URI，*表示全部允许，在SpringMVC中，如果设成*，会自动转成当前请求头中的Origin
        config.addAllowedOrigin("*");
        // 允许全部请求头，*表示全部
        config.addAllowedHeader("*");
        // 允许所有请求方法类型，*表示全部允许
        config.addAllowedMethod("*");
        // 预检请求的缓存时间-秒，即在这个时间段里，对于相同的跨域请求不会再预检了
        config.setMaxAge(18000L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        // 允许全部请求路径
        source.registerCorsConfiguration("/**", config);
        return source;
    }

    /**
     * Jackson全局转化long类型为String，解决jackson序列化时传入前端Long类型缺失精度问题
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
            jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
            jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);

            //全局时间格式化 去掉 "T"
            jacksonObjectMapperBuilder.serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            jacksonObjectMapperBuilder.deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        };
    }

    /**
     * 单独配置内部调用的登录路由
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("oauth-password-login-route", r -> r.path("/api/oauth/user/token").and().query("type", "password")
                        .filters(f -> f
                                // 重写转发的路由
                                .rewritePath("/api/oauth/user/token", "/oauth/token")
                                .addRequestParameter("client_id", oauthClientProperties.getClientId())
                                .addRequestParameter("client_secret", oauthClientProperties.getClientSecret())
                                .addRequestParameter("grant_type", "password"))
                        .uri("lb://oauth-server"))
                .route("oauth-code-login-route", r -> r.path("/api/oauth/user/token").and().query("type", "code")
                        .filters(f -> f
                                // 重写转发的路由
                                .rewritePath("/api/oauth/user/token", "/oauth/token")
                                .addRequestParameter("client_id", oauthClientProperties.getClientId())
                                .addRequestParameter("client_secret", oauthClientProperties.getClientSecret())
                                .addRequestParameter("grant_type", "sms_code"))
                        .uri("lb://oauth-server"))
                .route("oauth-refresh-token-route", r -> r.path("/api/oauth/user/refresh_token")
                        .filters(f -> f
                                // 重写转发的路由
                                .rewritePath("/api/oauth/user/refresh_token", "/oauth/token")
                                .addRequestParameter("client_id", oauthClientProperties.getClientId())
                                .addRequestParameter("client_secret", oauthClientProperties.getClientSecret())
                                .addRequestParameter("grant_type", "refresh_token"))
                        .uri("lb://oauth-server"))
                .route("oauth-logout-route", r -> r.path("/api/oauth/user/logout")
                        .filters(f -> f
                                // 重写转发的路由
                                .rewritePath("/api/oauth/user/logout", "/oauth/logout"))
                        .uri("lb://oauth-server"))
                .route("ws-admin-route", r -> r.path("/websocket/admin/**")
                        // 使用服务名进行负载均衡调用
                        .uri("lb:ws://admin-server"))
                .build();
    }

}
